package httputils

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"math/rand"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"os/user"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"fyne.io/fyne/v2/dialog"
)

const (
	RN       = "\r\n"
	RAND_STR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

var (
	headerMap = make(map[string]string, 0)
	cookieMap = make(map[string]string, 0)
)

type HttpService struct {
	ui   *HttpGui
	data *HttpData
}

func NewHttpService(ui *HttpGui) *HttpService {
	service := new(HttpService)
	service.ui = ui
	return service
}

type HttpData struct {
	inputGroup  []map[string]interface{}
	result      []string
	Name        string `json:"name"`
	Input       string `json:"input"`
	ForStatus   bool   `json:"forStatus"`
	SendType    bool   `json:"sendType"`
	ForTime     string `json:"forTime"`
	ExecStart   string `json:"execStart"`
	ExecEnd     string `json:"execEnd"`
	ForSize     string `json:"forSize"`
	ContentType string `json:"contentType"`
	Url         string `json:"url"`
	Method      string `json:"method"`
	Cookie      string `json:"cookie"`
	Header      string `json:"header"`
}

func (m *HttpService) readLine(buf *bytes.Buffer) string {
	lines := bytes.Buffer{}
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}

		line = strings.TrimSpace(line)
		if line == "" || strings.HasSuffix(strings.TrimSpace(line), "=") {
			continue
		} else if strings.HasPrefix(line, "#") {
			//注释
			continue
		} else if strings.HasSuffix(line, "=") {
			//未设置值/固定数据开始
			lines.WriteString(line)
			continue
		} else if strings.HasSuffix(line, ";") {
			//固定数据结果
			line = line[:len(line)-1]
			lines.WriteString(line)
			break
		} else if strings.Contains(line, "-END-") {
			//多报文结果
			lines.WriteString(line)
			break
		} else if !strings.Contains(line, "=") {
			//数据中无=号
			lines.WriteString(line)
			continue
		}
		lines.WriteString(line)
		break
	}
	return lines.String()
}

func (h *HttpService) readData() *HttpData {
	buf := bytes.NewBufferString(h.ui.Input.Text + "\n")
	h.data = new(HttpData)
	var item = make(map[string]interface{})
	h.data.inputGroup = []map[string]interface{}{}
	headers := make(map[string]string, 0)
	h.data.Url = h.ui.Url.Text
	h.data.Cookie = h.ui.Cookie.Text
	h.data.Header = h.ui.Header.Text
	h.data.ContentType = h.ui.ContentType.Selected
	h.data.Method = h.ui.Method.Selected
	for {
		line := h.readLine(buf)
		if line == "" {
			break
		}
		if strings.HasPrefix(line, "headers.") {
			idx := strings.Index(line, ".") + 1
			le := line[idx:]
			headers[strings.TrimSpace(le[0:strings.Index(le, "=")])] = strings.TrimRight(strings.TrimLeft(le[strings.Index(le, "=")+1:], " "), " ")
			continue
		}
		fmt.Println(h.data.Header)
		if h.data.Header != "" {
			kv := strings.Split(h.data.Header, ";")
			for _, item := range kv {
				nv := strings.Split(item, "=")
				if len(nv) == 2 {
					headers[strings.TrimSpace(nv[0])] = strings.TrimSpace(nv[1])
				}
			}
		}
		if h.data.Cookie != "" {
			headers["cookie"] = h.data.Cookie
		}
		if headers["Content-Type"] == "" {
			headers["Content-Type"] = h.ui.ContentType.Selected
		}
		fmt.Println(headers)
		item["headers"] = headers
		//报文分组标志
		if strings.Contains(line, "-END-") {
			h.data.inputGroup = append(h.data.inputGroup, item)
			item = make(map[string]interface{})
			headers = make(map[string]string, 0)
			continue
		}

		if !strings.Contains(line, "=") {
			if h.ui.SendType {
				val := item["data"]
				if val != nil {
					item["data"] = val.(string) + line
				}
			}
			continue
		}

		lines := [2]string{line[0:strings.Index(line, "=")], line[strings.Index(line, "=")+1:]}
		val := strings.TrimSpace(lines[1])
		if val == "" {
			continue
		}

		comp := regexp.MustCompile(`int:[\d]+`)
		idxs := comp.FindIndex([]byte(val))
		if len(idxs) == 2 {
			f := val[idxs[0]:idxs[1]]
			n, _ := strconv.Atoi(f[4:])
			rand.Seed(time.Now().UnixNano())
			nVal := fmt.Sprint(rand.Intn(n))
			val = strings.ReplaceAll(val, f, nVal)
		}

		comp = regexp.MustCompile(`date:[\d-:]+`)
		dateStrs := comp.FindAllString(val, -1)
		for _, dateItem := range dateStrs {
			var nVal string
			if dateItem == "date:timestamp" {
				nVal = fmt.Sprint(time.Now().Unix())
			} else {
				layout := dateItem[5:]
				nVal = time.Now().Format(layout)
			}
			val = strings.Replace(val, dateItem, nVal, 1)
		}

		comp = regexp.MustCompile(`str:[\d]+`)
		idxs = comp.FindIndex([]byte(val))
		if len(idxs) == 2 {
			f := val[idxs[0]:idxs[1]]
			length, _ := strconv.Atoi(f[4:])
			nVal := randomStr(length)
			val = strings.ReplaceAll(val, f, nVal)
		}
		item[strings.TrimSpace(lines[0])] = val
	}
	if len(item) != 0 {
		item["headers"] = headers
		h.data.inputGroup = append(h.data.inputGroup, item)
	}
	h.data.ExecStart = h.ui.ExecStart.Text
	h.data.ExecEnd = h.ui.ExecEnd.Text
	h.data.Name = h.ui.OldSelectValue
	h.data.Input = h.ui.Input.Text
	h.data.ForStatus = h.ui.ForStatus.Checked
	h.data.SendType = h.ui.SendType
	h.data.ForTime = h.ui.SleepSize.Text
	h.data.ForSize = h.ui.ForSize.Text
	h.data.result = []string{}
	start, _ := strconv.ParseInt(h.data.ExecStart, 10, 64)
	end, _ := strconv.ParseInt(h.data.ExecEnd, 10, 64)
	if (int(start) <= len(h.data.inputGroup) && int(start+1) != 1) || int(end) <= len(h.data.inputGroup) {
		var task []map[string]interface{}
		max := math.Min(float64(end), float64(len(h.data.inputGroup)))
		for i := int(start - 1); i < int(max); i++ {
			task = append(task, h.data.inputGroup[i])
		}
		h.data.inputGroup = task
	} else if h.data.Url != "" {
		var task []map[string]interface{}
		if h.data.Header != "" {
			kv := strings.Split(h.data.Header, ";")
			for _, item := range kv {
				nv := strings.Split(item, "=")
				if len(nv) == 2 {
					headers[strings.TrimSpace(nv[0])] = strings.TrimSpace(nv[1])
				}
			}
		}
		if h.data.Cookie != "" {
			headers["cookie"] = h.data.Cookie
		}
		if headers["Content-Type"] == "" {
			headers["Content-Type"] = h.ui.ContentType.Selected
		}
		item["headers"] = headers
		task = append(task, item)
		h.data.inputGroup = task
	}
	return h.data
}

func (h *HttpService) bindData(data *HttpData) {
	h.ui.ExecStart.SetText(data.ExecStart)
	h.ui.ExecEnd.SetText(data.ExecEnd)
	h.ui.OldSelect.SetText(data.Name)
	h.ui.OldSelectValue = data.Name
	h.ui.Input.SetText(data.Input)
	h.ui.ForStatus.SetChecked(data.ForStatus)
	h.ui.SendTypeUI.SetChecked(data.SendType)
	h.ui.SleepSize.SetText(data.ForTime)
	h.ui.ForSize.SetText(data.ForSize)
	h.ui.Url.SetText(data.Url)
	h.ui.ContentType.SetSelected(data.ContentType)
	h.ui.Method.SetSelected(data.Method)
	h.ui.Cookie.SetText(data.Cookie)
	h.ui.Header.SetText(data.Header)
}

// RandomStr 随机生成字符串
func randomStr(size int) string {
	bytes := []byte(RAND_STR)
	result := []byte{}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < size; i++ {
		result = append(result, bytes[r.Intn(len(bytes))])
	}
	return string(result)
}

func (h *HttpService) ExecSendHttp() {
	if h.ui.Url.Text == "" && h.ui.Input.Text == "" {
		h.PrintError(errors.New("请输入请求地址"))
		return
	}
	if h.ui.ForStatus.Checked {
		threadSize, _ := strconv.ParseInt(h.ui.ForSize.Text, 10, 64)
		sleepSize, _ := strconv.ParseInt(h.ui.SleepSize.Text, 10, 64)
		ch := make(chan int, int(threadSize))
		go func() {
			defer func() {
				recover()
			}()
			h.Println("处理中...")
			for {
				ch <- 1
				go h.sendHttp(ch)
				if !h.ui.ForStatus.Checked {
					break
				}
				a := time.Duration(sleepSize) * time.Millisecond
				time.Sleep(a)
			}
		}()
	} else {
		h.Println("处理中...")
		ch := make(chan int, 1)
		ch <- 1
		go h.sendHttp(ch)
		go func() {
			ch <- 1
		}()
	}
}

// 数据读取到map
func (h *HttpService) itemChange(data *HttpData, item map[string]interface{}) []string {
	var keys []string
	for k, v := range item {
		keys = append(keys, k)
		strVal, ok := v.(string)
		if !ok {
			continue
		}
		comp := regexp.MustCompile(`req:[0-9]+[:]+[a-zA-Z0-9\.]+`)
		aVals := comp.FindAllString(strVal, -1)
		for _, im := range aVals {
			vs := strings.Split(im, ":")
			if len(vs) != 3 {
				continue
			}
			var i, _ = strconv.Atoi(vs[1])
			val := vs[2]
			if strings.Contains(val, ".") {
				ks := strings.Split(val, ".")
				var inter = data.inputGroup[i]
				var retval string
				for l, s := range ks {
					if inter == nil {
						break
					}
					itv := inter[s]
					if itv == nil {
						inter = nil
						break
					}

					if (l + 1) != len(ks) {
						tiv, ok := itv.(string)
						if ok {
							var vMap map[string]interface{}
							err := json.Unmarshal([]byte(tiv), &vMap)
							if err == nil {
								inter = vMap
							}
						} else {
							miv, ok := itv.(map[string]interface{})
							if ok {
								inter = miv
							}
						}
						continue
					} else {
						retval, ok = itv.(string)
						if !ok {
							retbs, err := json.Marshal(itv)
							if err == nil {
								retval = string(retbs)
							}
						}
					}
				}
				strVal = strings.ReplaceAll(strVal, im, retval)
			} else {
				itmv := data.inputGroup[i][val]
				if itmv != nil {
					strVal = strings.ReplaceAll(strVal, im, itmv.(string))
				}
			}
		}

		comp = regexp.MustCompile(`res:[0-9]+[:]+[a-zA-Z0-9\.]+`)
		aVals = comp.FindAllString(strVal, -1)
		for _, im := range aVals {
			vs := strings.Split(im, ":")
			if len(vs) != 3 {
				continue
			}
			var i, _ = strconv.Atoi(vs[1])
			if len(data.result) <= i {
				continue
			}
			var inter map[string]interface{}
			err := json.Unmarshal([]byte(data.result[i]), &inter)
			if err != nil {
				continue
			}
			val := vs[2]
			if strings.Contains(val, ".") {
				ks := strings.Split(val, ".")
				var retval string
				for i, s := range ks {
					if inter == nil {
						break
					}
					itv := inter[s]
					if itv == nil {
						inter = nil
						break
					}

					if (i + 1) != len(ks) {
						var vMap map[string]interface{}
						err := json.Unmarshal([]byte(itv.(string)), &vMap)
						if err == nil {
							inter = vMap
						}
						continue
					} else {
						retval, ok = itv.(string)
						if !ok {
							retbs, err := json.Marshal(itv)
							if err == nil {
								retval = string(retbs)
							}
						}
					}
				}
				strVal = strings.ReplaceAll(strVal, im, retval)
			} else {
				itmv := data.inputGroup[i][val]
				if itmv != nil {
					strVal = strings.ReplaceAll(strVal, im, itmv.(string))
				}
			}
		}
		item[k] = strVal
	}
	sort.Strings(keys)
	return keys
}

func (h *HttpService) sendHttp(ch chan int) {
	defer func() {
		<-ch
	}()
	h.readData()
	for _, item := range h.data.inputGroup {
		h.itemChange(h.data, item)
		sleep, ok := item["sleep"].(string)
		if ok {
			delete(item, "sleep")
			out, _ := strconv.Atoi(sleep)
			time.Sleep(time.Second * time.Duration(out))
		}
		headers := item["headers"].(map[string]string)
		delete(item, "headers")
		addr, ok := item["url"].(string)
		if !ok || addr == "" {
			if addr == "" {
				addr = h.data.Url
			}
			if addr == "" {
				dialog.ShowInformation("警告", "URL不能为空！", h.ui.Win)
				return
			}
		}
		delete(item, "url")
		method, ok := item["method"].(string)
		if !ok {
			if method == "" {
				method = h.data.Method
			}
			if method == "" {
				dialog.ShowInformation("警告", "请求类型不能为空！", h.ui.Win)
				return
			}
		}
		delete(item, "method")
		if addr == "" {
			dialog.ShowInformation("警告", "URL不能为空！", h.ui.Win)
			return
		}
		h.Println("请求地址：" + addr)
		sendData, ok := item["data"].(string)
		var resp *http.Response
		var err error
		if h.data.SendType && ok && sendData != "" {
			resp, err = h.originalData(addr, method, sendData, headers)
		} else {
			if strings.Contains(headers["Content-Type"], "application/json") {
				resp, err = h.jsonData(addr, method, item, headers)
			} else if strings.Contains(headers["Content-Type"], "multipart/form-data") {
				resp, err = h.uploadData(addr, method, item, headers)
			} else {
				resp, err = h.formData(addr, method, item, headers)
			}
		}
		if err != nil {
			h.PrintError(err)
			return
		}
		defer resp.Body.Close()

		for k, v := range resp.Header {
			if k == "Date" || k == "Vary" || k == "Content-Type" {
				continue
			}
			if len(v) >= 1 {
				headerMap[k] = v[0]
			}
		}
		for _, v := range resp.Cookies() {
			cookieMap[v.Name] = v.Value
		}
		bs, err := io.ReadAll(resp.Body)
		if err != nil {
			h.PrintError(err)
			return
		}
		disp := resp.Header["Content-Disposition"]
		if len(disp) == 1 && strings.Contains(strings.ToLower(disp[0]), "filename") {
			u, _ := user.Current()
			start := strings.Index(disp[0], `"`)
			end := strings.LastIndex(disp[0], `"`)
			filename := disp[0]
			if start != -1 && end != -1 {
				filename = disp[0][start+1 : end]
			}

			os.WriteFile(u.HomeDir+"/Downloads/"+filename, bs, 0666)
			h.Println("文件已保存：" + u.HomeDir + "/Downloads/" + filename)
		} else {
			h.Println("响应信息：")
			var result = string(bs)
			h.PrintlnFormat(result)
			h.data.result = append(h.data.result, result)
		}
	}
}

// 表单数据
func (h *HttpService) formData(addr, method string, item map[string]interface{}, headers map[string]string) (*http.Response, error) {
	sdata := url.Values{}
	for k, v := range item {
		val, _ := v.(string)
		sdata.Add(k, val)
	}
	hs, _ := json.Marshal(headers)
	h.Println("Headers：" + string(hs))
	h.Println("请求数据：" + sdata.Encode())
	var req *http.Request
	var err error
	if strings.ToUpper(method) == "GET" {
		if strings.Contains(addr, "?") {
			addr = addr + "&" + sdata.Encode()
		} else {
			addr = addr + "?" + sdata.Encode()
		}
		req, err = http.NewRequest(method, addr, nil)
	} else {
		req, err = http.NewRequest(method, addr, strings.NewReader(sdata.Encode()))
	}
	if err != nil {
		return nil, err
	}

	for k, v := range headerMap {
		req.Header.Add(k, v)
	}
	for k, v := range cookieMap {
		ck := &http.Cookie{Name: k, Value: v, HttpOnly: true}
		req.AddCookie(ck)
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	client := &http.Client{}
	return client.Do(req)
}

// 文件上传
func (h *HttpService) uploadData(addr, method string, item map[string]interface{}, headers map[string]string) (*http.Response, error) {
	uploadName, ok := item["uploadName"].(string)
	if !ok {
		return nil, errors.New("上传文件时：[uploadName]标签名不能为空")
	}
	delete(item, "uploadName")
	filePath, ok := item["filePath"].(string)
	if !ok {
		return nil, errors.New("上传文件时：[filePath]文件路径不能为空")
	}
	delete(item, "filePath")
	buf := new(bytes.Buffer)
	bodyWriter := multipart.NewWriter(buf) // body writer

	f, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	for k, v := range item {
		vv, ok := v.(string)
		if ok {
			bodyWriter.WriteField(k, vv)
		}
	}
	fw, _ := bodyWriter.CreateFormFile(uploadName, f.Name())
	io.Copy(fw, f)
	bodyWriter.Close()
	contentType := bodyWriter.FormDataContentType()
	req, err := http.NewRequest(method, addr, buf)
	if err != nil {
		return nil, err
	}
	hs, _ := json.Marshal(headers)
	h.Println("Headers：" + string(hs))
	for k, v := range headerMap {
		req.Header.Add(k, v)
	}
	for k, v := range cookieMap {
		ck := &http.Cookie{Name: k, Value: v, HttpOnly: true}
		req.AddCookie(ck)
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	req.Header.Set("Content-Type", contentType)

	for k, v := range headers {
		req.Header.Add(k, v)
	}
	client := &http.Client{}
	return client.Do(req)
}

// json数据请求
func (h *HttpService) jsonData(addr, method string, item map[string]interface{}, headers map[string]string) (*http.Response, error) {
	client := &http.Client{}
	bs, err := json.Marshal(item)
	if err != nil {
		return nil, err
	}
	hs, _ := json.Marshal(headers)
	h.Println("Headers：" + string(hs))
	h.Println("请求数据：" + string(bs))

	req, err := http.NewRequest(method, addr, bytes.NewBuffer(bs))
	if err != nil {
		return nil, err
	}
	for k, v := range headerMap {
		req.Header.Add(k, v)
	}
	for k, v := range cookieMap {
		ck := &http.Cookie{Name: k, Value: v, HttpOnly: true}
		req.AddCookie(ck)
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return client.Do(req)
}

// 原始数据请求
func (h *HttpService) originalData(addr, method, sendData string, headers map[string]string) (*http.Response, error) {
	client := &http.Client{}
	hs, _ := json.Marshal(headers)
	h.Println("Headers：" + string(hs))
	h.Println(sendData)
	req, err := http.NewRequest(method, addr, strings.NewReader(sendData))
	if err != nil {
		dialog.ShowInformation("警告", "创建请求失败："+err.Error(), h.ui.Win)
		return nil, err
	}
	for k, v := range headerMap {
		req.Header.Add(k, v)
	}
	for k, v := range cookieMap {
		ck := &http.Cookie{Name: k, Value: v, HttpOnly: true}
		req.AddCookie(ck)
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return client.Do(req)
}

// 打印错误
func (h *HttpService) PrintError(err error) {
	dialog.ShowInformation("错误", err.Error(), h.ui.Win)
}

func (h *HttpService) PrintlnFormat(data string) {
	if h.ui.Format.Checked {
		data = strings.ReplaceAll(data, "{", RN+"{"+RN)
		data = strings.ReplaceAll(data, "}", RN+"}"+RN)
		data = strings.ReplaceAll(data, ",", ","+RN)
	}
	h.Println(data)
}

func (h *HttpService) Println(data string) {
	h.ui.Input.Hide()
	h.ui.Output.Show()
	if len(data) > 60000 {
		filename := "D:/" + time.Now().Format("2006-01-02-150405-") + "大文本网络响应.txt"
		os.WriteFile(filename, []byte(data), 0666)
		dialog.ShowInformation("提示", "接口响应文本过大，已写入磁盘："+filename, h.ui.Win)
	} else {
		h.ui.Output.Append(data)
		h.ui.Output.Append(RN)
	}
}
